package jeconbond.economic.market.agent.service;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import jeconbond.economic.agent.service.IPostActServiceAgent;
import jeconbond.economic.resources.IResourceType;
import jeconbond.economic.resources.ResourceBasket;
import jeconbond.economic.resources.ResourceStorrage;
import jeconbond.economic.resources.ResourceUtils;
import jeconbond.economic.systems.market.IMarketResourceRedestributionSystem;
import jeconbond.economic.systems.market.IMoneyAccount;
import jeconbond.economic.systems.market.ISellProposition;
import jeconbond.economic.systems.market.ISellsListener;
import jeconbond.economic.systems.market.MoneyAccount;
import laboratoryQ.BaseUtils;

public class PurchaseExceedResourcesAgent implements IPostActServiceAgent, ISellsListener {
	private IMarketResourceRedestributionSystem mrrs;
	
	private double utiliseExceedPeriod;
	private double dayBudget;
	static { System.out.println("\nBLOCKING CH_GUI implement private int passivePeriodRemaind; with IFF-service"); }
	private int passivePeriodRemaind;
	private int spyDayCostsTrackLength;
	
	private IMoneyAccount money = new MoneyAccount();
	private ResourceStorrage purchasedStorrage = new ResourceStorrage();
	

	private Map<IResourceType, double[]> resource2useTrack = new HashMap<IResourceType, double[]>();
	private int spyDayCostsTrackPos = 0;
	private int spyDayCostsTrackActivity = 0;
	
	private ResourceStorrage spyDayCostsBuffer = new ResourceStorrage(); // daily costs
	private ResourceStorrage averageUseBuffer = new ResourceStorrage(); // daily average from tracks
	private ResourceStorrage totalProducedResourceBuffer = 
		new ResourceStorrage();
	private Map<IResourceType, SpyAveragePriceData> resource2priceSpyBuffer = 
		new HashMap<IResourceType, SpyAveragePriceData>();
	
	public PurchaseExceedResourcesAgent(
		IMarketResourceRedestributionSystem mrrs,
		double utiliseExceedPeriod,
		double dayBudget,
		int passivePeriod,
		int spyDayCostsTrackLength, 
		IResourceType[] resources
	) {
		this.mrrs = mrrs;
		this.utiliseExceedPeriod = utiliseExceedPeriod;
		this.dayBudget = dayBudget;
		this.passivePeriodRemaind = passivePeriod;
		this.spyDayCostsTrackLength = spyDayCostsTrackLength;
		initialiseResourceInfoCells(resources);
	}

	private void initialiseResourceInfoCells(IResourceType[] resources) {
		for (IResourceType rt : resources) {
			resource2useTrack.put(rt, new double[spyDayCostsTrackLength]);
			resource2priceSpyBuffer.put(rt, new SpyAveragePriceData());
		}
	}

	@Override
	public void processPreProduceStage() {
		writeSpyDayCosts();
		if (passivePeriodRemaind > 0) {
			passivePeriodRemaind--;
		} else {
			processPurchaseExceedMission();
		}
	}

	@Override
	public void processProduceStage() {
	}

	@Override
	public void processPostProduceStage() {
		clearSpyDayCostsData();
	}
	
	static { System.out.println("\nBLOCKING remove with avaluate price"); }
	
	@Override
	public void onSellPerformed(ISellProposition proposition, double value, IMoneyAccount buyerAccount) {
		if (buyerAccount != money) {
			spyDayCostsBuffer.storeResource(proposition.getType(), value);
			SpyAveragePriceData saoData = resource2priceSpyBuffer.get(proposition.getType());
			saoData.value += value;
			saoData.cost += proposition.getPrice() * value;
		}
	}
	
	public ResourceStorrage getPurchasedStorrage() {
		return purchasedStorrage;
	}

	private void clearSpyDayCostsData() {
		spyDayCostsBuffer.clear();
		for (SpyAveragePriceData sapData : resource2priceSpyBuffer.values()) {
			sapData.cost = 0.0;
			sapData.value = 0.0;
		}
	}

	private void writeSpyDayCosts() {
		for (ResourceBasket rb : spyDayCostsBuffer.listBaskets()) {
			double[] track = resource2useTrack.get(rb.type);
			track[spyDayCostsTrackPos] = rb.value;
		}
		spyDayCostsTrackPos = BaseUtils.cycleNext(spyDayCostsTrackPos, spyDayCostsTrackLength);
		spyDayCostsTrackActivity = Math.min(spyDayCostsTrackLength, spyDayCostsTrackActivity + 1);
	}
	
	private void processPurchaseExceedMission() {
		money.changeBalance(dayBudget);
		computeAverageUseData();
		computeExceed();
		computeCostsPartData();
		purchaseExceed();
	}

	private void purchaseExceed() {
		ResourceStorrage exceedStorrage = totalProducedResourceBuffer;
		ResourceUtils.divideStorrage(exceedStorrage, utiliseExceedPeriod);
		double availMoney = money.getBalance();
		for (ResourceBasket rb : exceedStorrage.listBaskets()) {
			SpyAveragePriceData sapData = resource2priceSpyBuffer.get(rb.type);
			double resourcePurchMoney = availMoney * sapData.cost;
			purchaseBasket(rb, resourcePurchMoney);
		}
	}

	private void purchaseBasket(ResourceBasket rb, double budget) {
		double req = rb.value;
		while (req > 0.0) {
			double bought = 0.0;
			ISellProposition[] props = mrrs.getSellPropositionsList(rb.type);
			ResourceUtils.sortSellPropositions(props);
			for (ISellProposition p : props) {
				double val = Math.min(p.getSellerStorrage().howMuchWasStored(rb.type), req);
				val = Math.min(val, budget / p.getPrice());
				budget -= val * p.getPrice();
				ResourceUtils.processDeal(p, val, money, purchasedStorrage, mrrs);
				bought += val;
				req -= val;
			}
			req -= bought;
			if (bought < 1e-5) {
				break;
			}
		}
	}

	private void computeExceed() {
		totalProducedResourceBuffer.clear();
		for (ISellProposition prop : mrrs.getSellPropositionsCollection()) {
			IResourceType type = prop.getType();
			double value = prop.getSellerStorrage().howMuchWasStored(type);
			totalProducedResourceBuffer.storeResource(type, value);
		}
		ResourceUtils.substractStorrages(
				totalProducedResourceBuffer, 
				averageUseBuffer, 
				true
		);
	}

	private void computeCostsPartData() {
		double costSum = 0.0;
		for (Entry<IResourceType, SpyAveragePriceData> entry : resource2priceSpyBuffer.entrySet()) {
			SpyAveragePriceData sapData = entry.getValue();
			IResourceType type = entry.getKey();
			double sellsBasedPrice = sapData.cost / sapData.value;
			double expPrice = ResourceUtils.expectedPrice(type, mrrs);
			double price = (sellsBasedPrice + expPrice) / 2.0;
			if (sapData.value == 0.0) {
				price = expPrice;
			}
			sapData.value = spyDayCostsBuffer.howMuchWasStored(type);
			sapData.cost = price * totalProducedResourceBuffer.howMuchWasStored(type);
			costSum += sapData.cost;
		}
		for (SpyAveragePriceData sapData : resource2priceSpyBuffer.values()) {
			sapData.value = 0.0;
			sapData.cost /= costSum;
		}
	}
	
	private void computeAverageUseData() {
		averageUseBuffer.clear();
		for (Entry<IResourceType, double[]> entry : resource2useTrack.entrySet()) {
			double[] track = entry.getValue();
			double average = getAverage(track);
			averageUseBuffer.storeResource(entry.getKey(), average);
		}
	}

	private double getAverage(double[] track) {
		double sum = 0.0;
		for (double d : track) {
			sum += d;
		}
		return sum / spyDayCostsTrackActivity;
	}
}
